package com.wgo.bpot.domain.common;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;


/**
 * All units reported are in Kilobytes
 *
 */
public class MemoryUtil {
    private Map<String,Long> totalCounter = new HashMap<String,Long>();
    private Map<String,Long> counter = new HashMap<String,Long>();
    private List<Map.Entry<String, Long>> recordedMemoryUsage = new ArrayList<Map.Entry<String, Long>>();
    private long initialUsedMemory;
    private long lastUsedMemory;
    private int createLogEntryEveryKb = 2000; // Will log every time the change is more than 2000kb
    private boolean isLoggingEnabled = true;
    private boolean isEnabled = true;
    public static final String PROPERTY_NAME_FOR_LOGGING = "memoryLoggingEnabled";
    public static final String PROPERTY_NAME_FOR_GC_ENABLED = "memoryGcEnabled";
    public static final String PROPERTY_NAME_FOR_ENABLED = "memoryEnabled";
    private static final transient Logger log = Logger.getLogger(MemoryUtil.class.getName());

    public MemoryUtil(boolean isLoggingEnabled){
        lastUsedMemory = getUsedMemoryInKb();
        initialUsedMemory = lastUsedMemory;
        this.isLoggingEnabled = isLoggingEnabled;
        setStateBasedOnProperties();
    }

    private void setStateBasedOnProperties() {
        String strB = System.getProperty(PROPERTY_NAME_FOR_LOGGING);
        if (null != strB) {
            this.isLoggingEnabled = Boolean.parseBoolean(strB);
        }
        strB = System.getProperty(PROPERTY_NAME_FOR_ENABLED);
        if (null != strB) {
            this.isEnabled = Boolean.parseBoolean(strB);
        }
    }
    
    public MemoryUtil() {
        this(true);
    }

    public static long getUsedMemoryInKb(){
        String strB = System.getProperty(PROPERTY_NAME_FOR_GC_ENABLED);
        if ((null != strB) && (Boolean.parseBoolean(strB))) {
            for (int i = 0; i < 5; i++) {
                System.gc();
            }
        }
        long usedBytes = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
        return usedBytes / 1024;
    }
    
    public void increaseCounter(String name){
        if (isEnabled) {
            increaseCounter(name,counter);
            increaseCounter(name,totalCounter);
            checkMemoryUsage();
        }
    }
    
    private static void increaseCounter(String name,Map<String,Long> counter){
        Long count = counter.get(name);
        if (null == count) {
            count = new Long(0);
        }
        counter.put(name,count + 1);
    }
    
    
    public boolean isLoggingEnabled() {
        return isLoggingEnabled;
    }

    public void setLoggingEnabled(boolean isLoggingEnabled) {
        this.isLoggingEnabled = isLoggingEnabled;
    }

    public void recordMemory(String key){
        Map<String, Long> m = new HashMap<String, Long>();
        m.put(key, getUsedMemoryInKb());
        recordedMemoryUsage.add(m.entrySet().iterator().next());
    }
    
    public List<Map.Entry<String, Long>> getRecordedMemoryUsageInKb(){
        return recordedMemoryUsage;
    }
    
    public Long getRecordedMemoryUsageInKb(String key){
        for (Map.Entry<String, Long> entry : recordedMemoryUsage) {
            if (key.equals(entry.getKey())) {
                return entry.getValue();
            }
        }
        return null;
    }
    
    public long getCounter(String name){
        Long count = counter.get(name);
        return (null == count) ? 0 : count;
    }

    public long getTotalCounter(String name){
        Long count = totalCounter.get(name);
        return (null == count) ? 0 : count;
    }

    public long getInitialUsedMemory() {
        return initialUsedMemory;
    }

    public void setInitialUsedMemory(long initialUsedMemory) {
        this.initialUsedMemory = initialUsedMemory;
    }
    
    private void checkMemoryUsage(){
        long newResult = getUsedMemoryInKb();
        long difference = newResult - lastUsedMemory;
        if (Math.abs(difference) > createLogEntryEveryKb) {
            if (isLoggingEnabled) {
                String diffStr = (0 < difference) ? "increased" : "decreased";
                log.info("Memory used has " + diffStr + " " + Math.abs(difference) + "kb to " + newResult + "kb.");
                log.info("The count(s) are now: " + totalCounter);
            }
            lastUsedMemory = newResult;
            counter.clear();
        }
    }

    public int getCreateLogEntryEveryKb() {
        return createLogEntryEveryKb;
    }

    public void setCreateLogEntryEveryKb(int createLogEntryEveryKb) {
        this.createLogEntryEveryKb = createLogEntryEveryKb;
    }
    
    
}
